bootstrap-notify.js ➔ ?!?   B
last analyzed

Complexity

Conditions 1
Paths 1152

Size

Total Lines 396
Code Lines 277

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 277
nc 1152
nop 1
dl 0
loc 396
rs 7
c 1
b 0
f 0

16 Functions

Rating   Name   Duplication   Size   Complexity  
A bootstrap-notify.js ➔ ... ➔ $.notify 0 4 1
A bootstrap-notify.js ➔ ... ➔ $.notifyDefaults 0 4 1
A bootstrap-notify.js ➔ ... ➔ $.extend.buildNotify 0 11 5
A bootstrap-notify.js ➔ ... ➔ $.extend.styleURL 0 11 1
B bootstrap-notify.js ➔ ... ➔ $.extend.init 0 67 3
A bootstrap-notify.js ➔ ... ➔ $.extend.setIcon 0 13 3
A bootstrap-notify.js ➔ ... ➔ String.format 0 9 1
B bootstrap-notify.js ➔ ... ➔ $.notifyClose 0 12 8
A bootstrap-notify.js ➔ ... ➔ $.extend.close 0 30 2
A bootstrap-notify.js ➔ ... ➔ $.notifyCloseExcept 0 8 5
C bootstrap-notify.js ➔ ... ➔ $.extend.placement 0 64 9
A bootstrap-notify.js ➔ ... ➔ $.extend.bind 0 38 3
C bootstrap-notify.js ➔ ... ➔ Notify 0 35 11
A bootstrap-notify.js ➔ ... ➔ $.extend.reposition 0 12 2
A bootstrap-notify.js ➔ ... ➔ $.extend.styleDismiss 0 9 1
A bootstrap-notify.js ➔ ... ➔ isDuplicateNotification 0 24 1

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
/*
2
3
4
5
     Creative Tim Modifications
6
7
     Lines: 238, 239 was changed from top: 5px to top: 50% and we added margin-top: -13px. In this way the close button will be aligned vertically
8
     Line:222 - modified when the icon is set, we add the class "alert-with-icon", so there will be enough space for the icon.
9
10
11
12
13
*/
14
15
16
/*
17
 * Project: Bootstrap Notify = v3.1.5
18
 * Description: Turns standard Bootstrap alerts into "Growl-like" notifications.
19
 * Author: Mouse0270 aka Robert McIntosh
20
 * License: MIT License
21
 * Website: https://github.com/mouse0270/bootstrap-growl
22
 */
23
24
/* global define:false, require: false, jQuery:false */
25
26
(function(factory) {
27
  if (typeof define === 'function' && define.amd) {
0 ignored issues
show
Bug introduced by
The variable define seems to be never declared. If this is a global, consider adding a /** global: define */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
28
    // AMD. Register as an anonymous module.
29
    define(['jquery'], factory);
30
  } else if (typeof exports === 'object') {
31
    // Node/CommonJS
32
    factory(require('jquery'));
33
  } else {
34
    // Browser globals
35
    factory(jQuery);
36
  }
37
}(function($) {
38
  // Create the defaults once
39
  var defaults = {
40
    element: 'body',
41
    position: null,
42
    type: "info",
43
    allow_dismiss: true,
44
    allow_duplicates: true,
45
    newest_on_top: false,
46
    showProgressbar: false,
47
    placement: {
48
      from: "top",
49
      align: "right"
50
    },
51
    offset: 20,
52
    spacing: 10,
53
    z_index: 1060,
54
    delay: 5000,
55
    timer: 1000,
56
    url_target: '_blank',
57
    mouse_over: null,
58
    animate: {
59
      enter: 'animated fadeInDown',
60
      exit: 'animated fadeOutUp'
61
    },
62
    onShow: null,
63
    onShown: null,
64
    onClose: null,
65
    onClosed: null,
66
    onClick: null,
67
    icon_type: 'class',
68
    template: '<div data-notify="container" class="col-11 col-md-4 alert alert-{0}" role="alert"><button type="button" aria-hidden="true" class="close" data-notify="dismiss"><i class="nc-icon nc-simple-remove"></i></button><span data-notify="icon"></span> <span data-notify="title">{1}</span> <span data-notify="message">{2}</span><div class="progress" data-notify="progressbar"><div class="progress-bar progress-bar-{0}" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%;"></div></div><a href="{3}" target="{4}" data-notify="url"></a></div>'
69
  };
70
71
  String.format = function() {
0 ignored issues
show
Compatibility Best Practice introduced by
You are extending the built-in type String. This may have unintended consequences on other objects using this built-in type. Consider subclassing instead.
Loading history...
72
    var args = arguments;
73
    var str = arguments[0];
74
    return str.replace(/(\{\{\d\}\}|\{\d\})/g, function(str) {
75
      if (str.substring(0, 2) === "{{") return str;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
76
      var num = parseInt(str.match(/\d/)[0]);
77
      return args[num + 1];
78
    });
79
  };
80
81
  function isDuplicateNotification(notification) {
82
    var isDupe = false;
83
84
    $('[data-notify="container"]').each(function(i, el) {
85
      var $el = $(el);
86
      var title = $el.find('[data-notify="title"]').html().trim();
87
      var message = $el.find('[data-notify="message"]').html().trim();
88
89
      // The input string might be different than the actual parsed HTML string!
90
      // (<br> vs <br /> for example)
91
      // So we have to force-parse this as HTML here!
92
      var isSameTitle = title === $("<div>" + notification.settings.content.title + "</div>").html().trim();
93
      var isSameMsg = message === $("<div>" + notification.settings.content.message + "</div>").html().trim();
94
      var isSameType = $el.hasClass('alert-' + notification.settings.type);
95
96
      if (isSameTitle && isSameMsg && isSameType) {
97
        //we found the dupe. Set the var and stop checking.
98
        isDupe = true;
99
      }
100
      return !isDupe;
101
    });
102
103
    return isDupe;
104
  }
105
106
  function Notify(element, content, options) {
107
    // Setup Content of Notify
108
    var contentObj = {
109
      content: {
110
        message: typeof content === 'object' ? content.message : content,
111
        title: content.title ? content.title : '',
112
        icon: content.icon ? content.icon : '',
113
        url: content.url ? content.url : '#',
114
        target: content.target ? content.target : '-'
115
      }
116
    };
117
118
    options = $.extend(true, {}, contentObj, options);
119
    this.settings = $.extend(true, {}, defaults, options);
120
    this._defaults = defaults;
121
    if (this.settings.content.target === "-") {
122
      this.settings.content.target = this.settings.url_target;
123
    }
124
    this.animations = {
125
      start: 'webkitAnimationStart oanimationstart MSAnimationStart animationstart',
126
      end: 'webkitAnimationEnd oanimationend MSAnimationEnd animationend'
127
    };
128
129
    if (typeof this.settings.offset === 'number') {
130
      this.settings.offset = {
131
        x: this.settings.offset,
132
        y: this.settings.offset
133
      };
134
    }
135
136
    //if duplicate messages are not allowed, then only continue if this new message is not a duplicate of one that it already showing
137
    if (this.settings.allow_duplicates || (!this.settings.allow_duplicates && !isDuplicateNotification(this))) {
138
      this.init();
139
    }
140
  }
141
142
  $.extend(Notify.prototype, {
143
    init: function() {
144
      var self = this;
145
146
      this.buildNotify();
147
      if (this.settings.content.icon) {
148
        this.setIcon();
149
      }
150
      if (this.settings.content.url != "#") {
151
        this.styleURL();
152
      }
153
      this.styleDismiss();
154
      this.placement();
155
      this.bind();
156
157
      this.notify = {
158
        $ele: this.$ele,
159
        update: function(command, update) {
160
          var commands = {};
161
          if (typeof command === "string") {
162
            commands[command] = update;
163
          } else {
164
            commands = command;
165
          }
166
          for (var cmd in commands) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
167
            switch (cmd) {
168
              case "type":
169
                this.$ele.removeClass('alert-' + self.settings.type);
170
                this.$ele.find('[data-notify="progressbar"] > .progress-bar').removeClass('progress-bar-' + self.settings.type);
171
                self.settings.type = commands[cmd];
172
                this.$ele.addClass('alert-' + commands[cmd]).find('[data-notify="progressbar"] > .progress-bar').addClass('progress-bar-' + commands[cmd]);
173
                break;
174
              case "icon":
175
                var $icon = this.$ele.find('[data-notify="icon"]');
176
                if (self.settings.icon_type.toLowerCase() === 'class') {
177
                  $icon.removeClass(self.settings.content.icon).addClass(commands[cmd]);
178
                } else {
179
                  if (!$icon.is('img')) {
180
                    $icon.find('img');
181
                  }
182
                  $icon.attr('src', commands[cmd]);
183
                }
184
                self.settings.content.icon = commands[command];
185
                break;
186
              case "progress":
187
                var newDelay = self.settings.delay - (self.settings.delay * (commands[cmd] / 100));
188
                this.$ele.data('notify-delay', newDelay);
189
                this.$ele.find('[data-notify="progressbar"] > div').attr('aria-valuenow', commands[cmd]).css('width', commands[cmd] + '%');
190
                break;
191
              case "url":
192
                this.$ele.find('[data-notify="url"]').attr('href', commands[cmd]);
193
                break;
194
              case "target":
195
                this.$ele.find('[data-notify="url"]').attr('target', commands[cmd]);
196
                break;
197
              default:
198
                this.$ele.find('[data-notify="' + cmd + '"]').html(commands[cmd]);
199
            }
200
          }
201
          var posX = this.$ele.outerHeight() + parseInt(self.settings.spacing) + parseInt(self.settings.offset.y);
202
          self.reposition(posX);
203
        },
204
        close: function() {
205
          self.close();
206
        }
207
      };
208
209
    },
210
    buildNotify: function() {
211
      var content = this.settings.content;
212
      this.$ele = $(String.format(this.settings.template, this.settings.type, content.title, content.message, content.url, content.target));
213
      this.$ele.attr('data-notify-position', this.settings.placement.from + '-' + this.settings.placement.align);
214
      if (!this.settings.allow_dismiss) {
215
        this.$ele.find('[data-notify="dismiss"]').css('display', 'none');
216
      }
217
      if ((this.settings.delay <= 0 && !this.settings.showProgressbar) || !this.settings.showProgressbar) {
218
        this.$ele.find('[data-notify="progressbar"]').remove();
219
      }
220
    },
221
    setIcon: function() {
222
      this.$ele.addClass('alert-with-icon');
223
224
      if (this.settings.icon_type.toLowerCase() === 'class') {
225
        this.$ele.find('[data-notify="icon"]').addClass(this.settings.content.icon);
226
      } else {
227
        if (this.$ele.find('[data-notify="icon"]').is('img')) {
228
          this.$ele.find('[data-notify="icon"]').attr('src', this.settings.content.icon);
229
        } else {
230
          this.$ele.find('[data-notify="icon"]').append('<img src="' + this.settings.content.icon + '" alt="Notify Icon" />');
231
        }
232
      }
233
    },
234
    styleDismiss: function() {
235
      this.$ele.find('[data-notify="dismiss"]').css({
236
        position: 'absolute',
237
        right: '10px',
238
        top: '50%',
239
        marginTop: '-13px',
240
        zIndex: this.settings.z_index + 2
241
      });
242
    },
243
    styleURL: function() {
244
      this.$ele.find('[data-notify="url"]').css({
245
        backgroundImage: 'url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)',
246
        height: '100%',
247
        left: 0,
248
        position: 'absolute',
249
        top: 0,
250
        width: '100%',
251
        zIndex: this.settings.z_index + 1
252
      });
253
    },
254
    placement: function() {
255
      var self = this,
256
        offsetAmt = this.settings.offset.y,
257
        css = {
258
          display: 'inline-block',
259
          margin: '0px auto',
260
          position: this.settings.position ? this.settings.position : (this.settings.element === 'body' ? 'fixed' : 'absolute'),
261
          transition: 'all .5s ease-in-out',
262
          zIndex: this.settings.z_index
263
        },
264
        hasAnimation = false,
265
        settings = this.settings;
266
267
      $('[data-notify-position="' + this.settings.placement.from + '-' + this.settings.placement.align + '"]:not([data-closing="true"])').each(function() {
268
        offsetAmt = Math.max(offsetAmt, parseInt($(this).css(settings.placement.from)) + parseInt($(this).outerHeight()) + parseInt(settings.spacing));
269
      });
270
      if (this.settings.newest_on_top === true) {
271
        offsetAmt = this.settings.offset.y;
272
      }
273
      css[this.settings.placement.from] = offsetAmt + 'px';
274
275
      switch (this.settings.placement.align) {
276
        case "left":
277
        case "right":
278
          css[this.settings.placement.align] = this.settings.offset.x + 'px';
279
          break;
280
        case "center":
281
          css.left = 0;
282
          css.right = 0;
283
          break;
284
      }
285
      this.$ele.css(css).addClass(this.settings.animate.enter);
286
      $.each(Array('webkit-', 'moz-', 'o-', 'ms-', ''), function(index, prefix) {
287
        self.$ele[0].style[prefix + 'AnimationIterationCount'] = 1;
288
      });
289
290
      $(this.settings.element).append(this.$ele);
291
292
      if (this.settings.newest_on_top === true) {
293
        offsetAmt = (parseInt(offsetAmt) + parseInt(this.settings.spacing)) + this.$ele.outerHeight();
294
        this.reposition(offsetAmt);
295
      }
296
297
      if ($.isFunction(self.settings.onShow)) {
298
        self.settings.onShow.call(this.$ele);
299
      }
300
301
      this.$ele.one(this.animations.start, function() {
302
        hasAnimation = true;
303
      }).one(this.animations.end, function() {
304
        self.$ele.removeClass(self.settings.animate.enter);
305
        if ($.isFunction(self.settings.onShown)) {
306
          self.settings.onShown.call(this);
307
        }
308
      });
309
310
      setTimeout(function() {
311
        if (!hasAnimation) {
312
          if ($.isFunction(self.settings.onShown)) {
313
            self.settings.onShown.call(this);
314
          }
315
        }
316
      }, 600);
317
    },
318
    bind: function() {
319
      var self = this;
320
321
      this.$ele.find('[data-notify="dismiss"]').on('click', function() {
322
        self.close();
323
      });
324
325
      if ($.isFunction(self.settings.onClick)) {
326
        this.$ele.on('click', function(event) {
327
          if (event.target != self.$ele.find('[data-notify="dismiss"]')[0]) {
328
            self.settings.onClick.call(this, event);
329
          }
330
        });
331
      }
332
333
      this.$ele.mouseover(function() {
334
        $(this).data('data-hover', "true");
335
      }).mouseout(function() {
336
        $(this).data('data-hover', "false");
337
      });
338
      this.$ele.data('data-hover', "false");
339
340
      if (this.settings.delay > 0) {
341
        self.$ele.data('notify-delay', self.settings.delay);
342
        var timer = setInterval(function() {
343
          var delay = parseInt(self.$ele.data('notify-delay')) - self.settings.timer;
344
          if ((self.$ele.data('data-hover') === 'false' && self.settings.mouse_over === "pause") || self.settings.mouse_over != "pause") {
345
            var percent = ((self.settings.delay - delay) / self.settings.delay) * 100;
346
            self.$ele.data('notify-delay', delay);
347
            self.$ele.find('[data-notify="progressbar"] > div').attr('aria-valuenow', percent).css('width', percent + '%');
348
          }
349
          if (delay <= -(self.settings.timer)) {
350
            clearInterval(timer);
351
            self.close();
352
          }
353
        }, self.settings.timer);
354
      }
355
    },
356
    close: function() {
357
      var self = this,
358
        posX = parseInt(this.$ele.css(this.settings.placement.from)),
359
        hasAnimation = false;
360
361
      this.$ele.attr('data-closing', 'true').addClass(this.settings.animate.exit);
362
      self.reposition(posX);
363
364
      if ($.isFunction(self.settings.onClose)) {
365
        self.settings.onClose.call(this.$ele);
366
      }
367
368
      this.$ele.one(this.animations.start, function() {
369
        hasAnimation = true;
370
      }).one(this.animations.end, function() {
371
        $(this).remove();
372
        if ($.isFunction(self.settings.onClosed)) {
373
          self.settings.onClosed.call(this);
374
        }
375
      });
376
377
      setTimeout(function() {
378
        if (!hasAnimation) {
379
          self.$ele.remove();
380
          if (self.settings.onClosed) {
381
            self.settings.onClosed(self.$ele);
382
          }
383
        }
384
      }, 600);
385
    },
386
    reposition: function(posX) {
387
      var self = this,
388
        notifies = '[data-notify-position="' + this.settings.placement.from + '-' + this.settings.placement.align + '"]:not([data-closing="true"])',
389
        $elements = this.$ele.nextAll(notifies);
390
      if (this.settings.newest_on_top === true) {
391
        $elements = this.$ele.prevAll(notifies);
392
      }
393
      $elements.each(function() {
394
        $(this).css(self.settings.placement.from, posX);
395
        posX = (parseInt(posX) + parseInt(self.settings.spacing)) + $(this).outerHeight();
396
      });
397
    }
398
  });
399
400
  $.notify = function(content, options) {
401
    var plugin = new Notify(this, content, options);
402
    return plugin.notify;
403
  };
404
  $.notifyDefaults = function(options) {
405
    defaults = $.extend(true, {}, defaults, options);
406
    return defaults;
407
  };
408
409
  $.notifyClose = function(selector) {
410
411
    if (typeof selector === "undefined" || selector === "all") {
412
      $('[data-notify]').find('[data-notify="dismiss"]').trigger('click');
413
    } else if (selector === 'success' || selector === 'info' || selector === 'warning' || selector === 'danger') {
414
      $('.alert-' + selector + '[data-notify]').find('[data-notify="dismiss"]').trigger('click');
415
    } else if (selector) {
416
      $(selector + '[data-notify]').find('[data-notify="dismiss"]').trigger('click');
417
    } else {
418
      $('[data-notify-position="' + selector + '"]').find('[data-notify="dismiss"]').trigger('click');
419
    }
420
  };
421
422
  $.notifyCloseExcept = function(selector) {
423
424
    if (selector === 'success' || selector === 'info' || selector === 'warning' || selector === 'danger') {
425
      $('[data-notify]').not('.alert-' + selector).find('[data-notify="dismiss"]').trigger('click');
426
    } else {
427
      $('[data-notify]').not(selector).find('[data-notify="dismiss"]').trigger('click');
428
    }
429
  };
430
431
432
}));